﻿package taurus.client.io;

import java.io.IOException;

import com.taurus.core.entity.ITObject;
import com.taurus.core.entity.TObject;
import com.taurus.core.util.*;

import taurus.client.SocketCode;

/**
 * 协议包字节流解析
 * 
 * @author daixiwei daixiwei15@126.com
 *
 */
class IOHandler {
	private final Logger			logger			= Logger.getLogger(IOHandler.class);
	private static final String		ACTION_ID		= "a";
	private static final String		PARAM_ID		= "p";
	static final int				INT_BYTE_SIZE	= 4;

	private static final ByteArray	EMPTY_BUFFER	= new ByteArray();
	private int						_skipBytes		= 0;
	private ByteArray				_buffer;
	private int						_expectedLength	= -1;
	private boolean					_compressed		= false;

	private SocketEngine			_engine;
	private SystemController		_controller;
	
	/**
	 * 读取状态
	 */
	private PacketReadState			_readState		= PacketReadState.WAIT_NEW_PACKET;

	IOHandler(SocketEngine client, SystemController controller) {
		_engine = client;
		_controller = controller;
	}

	/**
	 * 
	 * @param data
	 * @return
	 * @throws IOException
	 */
	private ByteArray handleDataSize(ByteArray data) throws Exception {
		int num = data.readInt();
		int pos = INT_BYTE_SIZE;
		if (num != -1) {
			_expectedLength = num;
			data = resizeByteArray(data, pos, data.length() - pos);
			_readState = PacketReadState.WAIT_DATA;
			return data;
		}
		_readState = PacketReadState.WAIT_DATA_SIZE_FRAGMENT;
		_buffer.writeBytes(data.bytes());
		data = EMPTY_BUFFER;
		return data;
	}

	/**
	 * 
	 * @param data
	 * @return
	 * @throws IOException
	 */
	private ByteArray handleDataSizeFragment(ByteArray data) throws Exception {
		int count = INT_BYTE_SIZE - _buffer.length();
		if (data.length() >= count) {
			_buffer.writeBytes(data.bytes(), 0, count);
			int num2 = INT_BYTE_SIZE;
			ByteArray array = new ByteArray();
			array.writeBytes(_buffer.bytes(), 0, num2);
			array.position(0);
			int num3 = array.readInt();
			_expectedLength = num3;
			_buffer = new ByteArray();
			_readState = PacketReadState.WAIT_DATA;
			if (data.length() > count) {
				data = resizeByteArray(data, count, data.length() - count);
				return data;
			}
			data = EMPTY_BUFFER;
			return data;
		}
		_buffer.writeBytes(data.bytes());
		data = EMPTY_BUFFER;
		return data;
	}

	/**
	 * 
	 * @param data
	 * @return
	 */
	private ByteArray handleInvalidData(ByteArray data) {
		if (_skipBytes == 0) {
			_readState = PacketReadState.WAIT_NEW_PACKET;
			return data;
		}
		int pos = Math.min(data.length(), _skipBytes);
		data = resizeByteArray(data, pos, data.length() - pos);
		_skipBytes -= pos;
		return data;
	}

	/**
	 * 
	 * @param data
	 * @return
	 */
	private ByteArray handleNewPacket(ByteArray data) throws Exception {
		_buffer = new ByteArray();
		_expectedLength = -1;
		_compressed = data.readByte() > 0;
		data = resizeByteArray(data, 1, data.length() - 1);
		_readState = PacketReadState.WAIT_DATA_SIZE;
		return data;
	}

	/**
	 * 
	 * @param data
	 * @return
	 */
	private ByteArray handlePacketData(ByteArray data) throws Exception {
		int count = _expectedLength - _buffer.length();
		boolean flag = data.length() > count;
		ByteArray array = new ByteArray(data.bytes());
		try {
			if (data.length() >= count) {
				_buffer.writeBytes(data.bytes(), 0, count);
				if (_compressed) {
					_buffer.uncompress();
				}
				dispatchRequest(_buffer);
				_readState = PacketReadState.WAIT_NEW_PACKET;
			} else {
				_buffer.writeBytes(data.bytes());
			}
			if (flag) {
				data = resizeByteArray(data, count, data.length() - count);
				return data;
			}
			data = EMPTY_BUFFER;
		} catch (Exception exception) {
			logger.error("Error handling data ", exception);
			_skipBytes = count;
			_readState = PacketReadState.INVALID_DATA;
			return array;
		}
		return data;
	}

	private void dispatchRequest(ByteArray _buffer) throws IOException {
		ITObject requestObject = TObject.newFromBinaryData(_buffer.bytes());
		Packet packet = new Packet();
		if (requestObject.isNull(ACTION_ID)) {
			throw new IOException("Request rejected: No Action ID in request!");
		}
		packet.actionID = requestObject.getByte(ACTION_ID);
		packet.parameters = requestObject.getTObject(PARAM_ID);
		_controller.handlePacket(packet);
	}

	/**
	 * 
	 * @param data
	 * @throws IOException
	 */
	public void onDataRead(ByteArray data) throws IOException {
		if (data.length() == 0) {
			throw new IOException("Unexpected empty packet data: no readable bytes available!");
		}

		data.position(0);
		try {
			while (data.length() > 0) {
				if (_readState == PacketReadState.WAIT_NEW_PACKET) {
					data = handleNewPacket(data);
				} else {
					if (_readState == PacketReadState.WAIT_DATA_SIZE) {
						data = handleDataSize(data);
						continue;
					}
					if (_readState == PacketReadState.WAIT_DATA_SIZE_FRAGMENT) {
						data = handleDataSizeFragment(data);
						continue;
					}
					if (_readState == PacketReadState.WAIT_DATA) {
						data = handlePacketData(data);
						continue;
					}
					if (_readState == PacketReadState.INVALID_DATA) {
						data = handleInvalidData(data);
					}
				}
			}
		} catch (Exception e) {
			this.logger.error(e);
		}

	}

	/**
	 * 
	 * @param packet
	 * @throws IOException
	 */
	public void onDataWrite(Packet packet) throws IOException {
		ByteArray binData = new ByteArray();

		ITObject obj2 = new TObject();
		obj2.putByte(ACTION_ID, (byte) packet.actionID);
		obj2.putTObject(PARAM_ID, packet.parameters);
		byte[] array2 = obj2.toBinary();
		boolean compression = array2.length > _engine.getProtocolCompression();
		if (compression) {
			array2 = Utils.compress(array2);
		}
		binData.writeByte((byte) (compression ? 1 : 0));
		binData.writeInt(array2.length);
		binData.writeBytes(array2);

		if (_engine._socket.isConnected()) {
			if (!_engine._socket.send(binData.bytes())) {
				_engine.enqueueStatusCallback(SocketCode.SendError);
			}
		}
	}

	/**
	 * 
	 * @param array
	 * @param pos
	 * @param len
	 * @return
	 */
	private ByteArray resizeByteArray(ByteArray array, int pos, int len) {
		byte[] dst = new byte[len];
		System.arraycopy(array.bytes(), pos, dst, 0, len);
		return new ByteArray(dst);
	}

	static enum PacketReadState {
		WAIT_NEW_PACKET, WAIT_DATA_SIZE, WAIT_DATA_SIZE_FRAGMENT, WAIT_DATA, INVALID_DATA
	}
}
